package com.de.util;

import com.de.annon.MybatisField;
import com.de.annon.MybatisTable;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import javax.validation.constraints.NotNull;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 *  
 *  * @projectName mybatis-demo
 *  * @title     MybatisAnnonUtil   
 *  * @package    com.de.util  
 *  * @description    自定义mybatis sql 映射注解工具类  
 *  * @author IT_CREAT     
 *  * @date  2020 2020/5/5 0005 下午 14:40  
 *  * @version V1.0.0 
 *  
 */
@Slf4j
public class MybatisAnnotationUtils {

    /**
     * 得到含有指定注解的类的属性名字的集合，返回list
     *
     * @param javaBean        java对象
     * @param annotationClass 注解class
     * @return 对象中含有指定注解的属性名称
     */
    public static List<String> getVarHaveAnnotation(@NotNull Object javaBean, Class<? extends Annotation> annotationClass) {
        List<String> varList = new ArrayList<>();
        if (ObjectUtils.isEmpty(javaBean) || ObjectUtils.isEmpty(annotationClass)) {
            return varList;
        }
        //变量
        Class<?> clazz = javaBean.getClass();
        Field[] fs = clazz.getDeclaredFields();
        Field[] superFs = clazz.getSuperclass().getDeclaredFields();
        for (Field field : fs) {
            if (field.isAnnotationPresent(annotationClass)) {
                varList.add(field.getName());
            }
        }
        for (Field field : superFs) {
            if (field.isAnnotationPresent(annotationClass)) {
                varList.add(field.getName());
            }
        }
        return varList;
    }

    /**
     * Map对象转化成 JavaBean对象 ,对象必须提供get和set方法，否则无法点对点赋值
     *
     * @param obj        对象
     * @param sqlFindMap Map对象
     * @return <T> T 如果map中的key在提供的obj中没有一个与之对应的属性则返回null
     */
    @SuppressWarnings("unchecked")
    public static <T> T sqlMapToJavaBean(@NotNull T obj, Map<String, Object> sqlFindMap) {
        return obj == null || sqlFindMap == null || sqlFindMap.isEmpty() ? null : (T) sqlMapToJavaBean(obj.getClass(), sqlFindMap);
    }

    /**
     * Map对象转化成 JavaBean对象 ,对象必须提供get和set方法，否则无法点对点赋值
     *
     * @param clazz      class对象
     * @param sqlFindMap Map对象
     * @return <T> T对象  clazz或sqlFindMap任意一个为空直接返回空,要返回的T对象中没有一个与sqlFindMap对应的key也返回null
     */
    public static <T> T sqlMapToJavaBean(@NotNull Class<T> clazz, Map<String, Object> sqlFindMap) {
        if (clazz == null || sqlFindMap == null || sqlFindMap.isEmpty()) {
            return null;
        }
        // 获取javaBean属性
        BeanInfo beanInfo;
        try {
            beanInfo = Introspector.getBeanInfo(clazz);
        } catch (IntrospectionException e) {
            log.error("sql map transfer java bean: get bean info exception", e);
            return null;
        }
        // 默认驼峰转换开启
        boolean isOpenFiledHumpTransfer = true;
        //判断有没有MybatisTable注解
        if (clazz.isAnnotationPresent(MybatisTable.class)) {
            MybatisTable[] annotationsByType = clazz.getAnnotationsByType(MybatisTable.class);
            //取最后一个,查看实体类开启驼峰转换没有
            isOpenFiledHumpTransfer = annotationsByType[0].isOpenFiledHumpTransfer();
        }
        // 创建 JavaBean 对象
        T obj;
        boolean isMapContainProperty = false;
        try {
            obj = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            log.error("sql map transfer java bean: new instant exception", e);
            return null;
        }
        // 获取对象所有属性,去除了静态、最终状态属性
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        if (ArrayUtils.isNotEmpty(propertyDescriptors)) {
            String propertyName; // javaBean属性名
            Object propertyValue; // javaBean属性值
            for (PropertyDescriptor pd : propertyDescriptors) {
                propertyName = pd.getName();
                MybatisField mybatisField = null;
                Field field = null;
                try {
                    field = clazz.getField(propertyName);
                } catch (NoSuchFieldException e) {
                    log.error("sql map transfer java bean: get bean filed exception", e);
                }
                //如果属性含有MybatisField字段
                if (field != null && field.isAnnotationPresent(MybatisField.class)) {
                    mybatisField = field.getAnnotationsByType(MybatisField.class)[0];
                    propertyName = StringUtils.isBlank(mybatisField.columnName()) ? propertyName : mybatisField.columnName();
                } else {
                    //如果设置了全局的全局的驼峰转换
                    if (isOpenFiledHumpTransfer) {
                        //转换成以小写下划线组成的格式
                        propertyName = HumpNamedUtils.hump2LowerColumnName(propertyName);
                    }
                }
                if (sqlFindMap.containsKey(propertyName)) {
                    isMapContainProperty = true;
                    propertyValue = sqlFindMap.get(propertyName);
                    try {
                        if (!"class".equals(propertyName)) {
                            //如果该字段是日期格式，则进行日期转换
                            if (mybatisField != null && mybatisField.isDate()) {
                                if (ObjectUtils.isNotEmpty(propertyValue)) {
                                    propertyValue = DateTimeUtils.parse((String) propertyValue, mybatisField.dateFormat());
                                } else {
                                    propertyValue = null;
                                }
                            }
                            pd.getWriteMethod().invoke(obj, propertyValue);
                        }
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        log.error("sql map transfer java bean: set filed " + propertyName + " value exception", e);
                    }
                }
            }
        }
        return ObjectUtils.isEmpty(obj) || !isMapContainProperty ? null : obj;
    }
}
